package cn.xiuxius.embedded.command.com;

import cn.xiuxius.embedded.comdev.ComTerminal;
import cn.xiuxius.embedded.comdev.MessageDispatcher;
import cn.xiuxius.embedded.comdev.SerialPortDataListenerImpl;
import cn.xiuxius.embedded.comdev.SerialPortManager;
import cn.xiuxius.embedded.config.EasyYamlConfig;
import com.fazecast.jSerialComm.SerialPort;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import picocli.CommandLine;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.Callable;

@CommandLine.Command(
        name = "open",
        description = "COM Open command."
)
@Slf4j
public class ComOpenCommand implements Callable<Integer> {


    @CommandLine.Option(names = {"-t", "--target"},
            description = "COM device name.",
            required = false
    )
    @Getter
    private String comName;

    @CommandLine.Option(names = {"-rate", "--baudrate"},
            description = "Baud rate of com.",
            required = false,
            defaultValue = "115200")
    private int baudRate;


    @Override
    public Integer call() {

        String portName;

        if (Objects.isNull(comName) || comName.isEmpty()) {
            EasyYamlConfig config = EasyYamlConfig.load();
            if (config == null) {
                return 1;
            }
            if (config.getStcgl().getPort() == null) {
                log.warn("Port not specified, will try to find the first available port.");
                portName = "AUTO";
            } else {
                portName = config.getStcgl().getPort().getValue();
            }
        } else {
            portName = this.comName;
        }

        if (portName.equals("AUTO")) {
            SerialPort selectPort;
            log.info("Port is auto, will try to find the first available port.");
            List<SerialPort> serialPorts = Arrays.stream(SerialPort.getCommPorts()).toList();

            if (serialPorts.isEmpty()) {
                log.error("No available ports found.");
                return 1;
            }

            //如果只有一个则选择这个
            if (serialPorts.size() == 1) {
                //找到唯一的端口 已自动选择此端口
                selectPort = serialPorts.getFirst();
                log.info("✅ Port is auto, find the only available port: {}", selectPort.getSystemPortName());
            } else {
                //找到多个 用户自己选择
                log.info("✅ Find multiple available ports:");
                for (int i = 0; i < serialPorts.size(); i++) {
                    log.info("{}: {}", i, serialPorts.get(i).getSystemPortName());
                }
                log.info("Please select the port you want to use:");
                Scanner scanner = new Scanner(System.in);
                if (scanner.hasNextInt()) {
                    int portIndex = scanner.nextInt();
                    if (portIndex < 0 || portIndex >= serialPorts.size()) {
                        log.error("Invalid input, please enter a number between 0 and {}.", serialPorts.size() - 1);
                        return 1;
                    }
                    selectPort = serialPorts.get(portIndex);
                } else {
                    log.error("Invalid input, please enter a number.");
                    return 1;
                }
            }
            portName = selectPort.getSystemPortName();
            log.info("Selected port: {}", portName);
            log.info("Port info: {}", selectPort.getDescriptivePortName());
            if (selectPort.isOpen()) {
                log.warn("Port is already open, will thy close it.");
                selectPort.closePort();
            }
        }

        SerialPort[] commPorts = SerialPort.getCommPorts();
        SerialPort serialPort = null;
        for (SerialPort commPort : commPorts) {
            if (commPort.getSystemPortName().equals(portName)) {
                serialPort = commPort;
                break;
            }
        }

        if (serialPort == null) {
            log.error("COM port {} not found.", portName);
            return 1;
        }

        serialPort.setBaudRate(baudRate);
        serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING | SerialPort.TIMEOUT_WRITE_BLOCKING, 1000, 1000);//设置超时
        serialPort.setRTS();//设置RTS。也可以设置DTR
        serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);//设置串口的控制流，可以设置为disabled，或者CTS, RTS/CTS, DSR, DTR/DSR, Xon, Xoff, Xon/Xoff等
        //先写死
        serialPort.setComPortParameters(baudRate, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);//一次性设置所有的串口参数，第一个参数为波特率，默认9600；第二个参数为每一位的大小，默认8，可以输入5到8之间的值；第三个参数为停止位大小，只接受内置常量，可以选择(ONE_STOP_BIT, ONE_POINT_FIVE_STOP_BITS, TWO_STOP_BITS)；第四位为校验位，同样只接受内置常量，可以选择 NO_PARITY, EVEN_PARITY, ODD_PARITY, MARK_PARITY,SPACE_PARITY。
        boolean isOpened = serialPort.openPort();
        if (!isOpened) {
            log.error("COM port {} open failed.", portName);
            return 1;
        }

        SerialPortManager serialPortManager = new SerialPortManager(serialPort);

        log.info("COM port {} opened.", portName);
        boolean openPort = serialPortManager.openPort();

        if (!openPort) {
            log.error("COM port {} open failed.", portName);
            return 1;
        }

        Terminal terminal;
        try {
            terminal = TerminalBuilder.builder()
                    .system(true)
                    .build();
        } catch (IOException e) {
            log.error("Terminal build failed.");
            return 1;
        }


        LineReader reader = LineReaderBuilder.builder()
                .terminal(terminal)
                .build();

        MessageDispatcher dispatcher = new MessageDispatcher(terminal, reader, serialPortManager);


        ComTerminal comTerminal = new ComTerminal(reader, dispatcher);

        SerialPortDataListenerImpl serialPortDataListener = new SerialPortDataListenerImpl(dispatcher);

        serialPortManager.addDataListener(serialPortDataListener);

        while (serialPortManager.getPort().isOpen() && comTerminal.run()) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                log.error("Thread sleep failed.", e);
            }
        }

        if (serialPortManager.getPort().isOpen()) {
            serialPortManager.closePort();
            log.info("COM port {} closed.", portName);
        }
        try {
            terminal.close();
        } catch (IOException e) {
            log.error("Terminal close failed.", e);
            return 1;
        }
        return 0;
    }

}
